#pragma once

#include <vex/functional/contract/invokable_event.h>
#include <vex/functional/implementation/multicast_delegate_default_impl.h>
#include <vex/core/make_shared_handle.h>
#include <memory>
#include <atomic>

namespace vex { namespace functional { namespace implementation {

    template<
        class TSignature,
        class TReducer = default_value_reducer<typename result<TSignature>::type>,
        class TInvokableEventContract = contract::invokable_event<
            TSignature,
            typename std::result_of<
                TReducer(contract::invoker_range<typename result<TSignature>::type>*)
            >::type
        > 
    > 
    class invokable_event_default_impl;

    namespace detail {

        using vex::core::make_shared_handle;

        template<class TSignature, class TReducer> 
        class backing_delegate_impl : public multicast_delegate_default_impl<TSignature, TReducer> {
            typedef multicast_delegate_default_impl<TSignature, TReducer> base_type;
            typedef backing_delegate_impl<TSignature, TReducer> this_type;
        protected:
            VEX_MULTICAST_DELEGATE_DEFAULT_IMPL_BODY(backing_delegate_impl);
        public:
            std::shared_ptr<this_type> combine_shared_ptr(delegate_type* p_delegate) const
            {
                inv_list_impl_type t_new_inv_list;
                return this->internal_combine(p_delegate, t_new_inv_list) ?
                    make_shared_handle< this_type >(this->m_reducer, t_new_inv_list) : 
                    std::shared_ptr<this_type>();
            }

            std::shared_ptr<this_type> remove_shared_ptr(delegate_type* p_delegate) const
            {
                inv_list_impl_type t_new_inv_list;
                return this->internal_remove(p_delegate, removal_mode::last, t_new_inv_list) ?
                    make_shared_handle< this_type >(this->m_reducer, t_new_inv_list) : 
                    std::shared_ptr<this_type>();
            }
        };

        template<class TSignature, class TReducer, class TInvokableEventContract>
        class VEX_ABSTRACT invokable_event_default_impl_base : public TInvokableEventContract {
            invokable_event_default_impl_base(invokable_event_default_impl_base const &);
            invokable_event_default_impl_base& operator=(invokable_event_default_impl_base const &);
        protected:
            typedef TSignature signature_type;
            typedef typename TInvokableEventContract::delegate_type delegate_type;
            typedef typename TInvokableEventContract::result_type result_type;
            typedef typename std::remove_reference<TReducer>::type reducer_type;
            typedef backing_delegate_impl<TSignature, TReducer> backing_delegate_type;
            typedef std::shared_ptr<backing_delegate_type> backing_delegate_impl_type;
            typedef typename backing_delegate_type::invocation_list_type invocation_list_type;

            backing_delegate_impl_type m_backing_delegate;

            invokable_event_default_impl_base()
                : m_backing_delegate(construct()) 
            {
            }

            template<class UReducer>
            invokable_event_default_impl_base(UReducer&& p_reducer)
                : m_backing_delegate(construct(std::forward<UReducer>(p_reducer))) 
            {
            }

            invokable_event_default_impl_base(invokable_event_default_impl_base&& p_other)
                : m_backing_delegate(std::move(p_other.m_backing_delegate))
            {
            }
        public:
            virtual bool add(delegate_type* p_delegate)
            {
                return this->compare_and_swap_loop([=](backing_delegate_impl_type const& p_current) {
                    return p_current->combine_shared_ptr(p_delegate);
                });
            }

            virtual bool remove(delegate_type* p_delegate)
            {
                return this->compare_and_swap_loop([=](backing_delegate_impl_type const& p_current) {
                    return p_current->remove_shared_ptr(p_delegate);
                });
            }

            virtual invocation_list_type* get_invocation_list() const
            {
                return std::atomic_load(&m_backing_delegate)
                    ->get_invocation_list();
            }
        private:
            static backing_delegate_impl_type construct()
            {
                return vex::core::make_shared_handle<
                    backing_delegate_impl<TSignature, TReducer>
                >(default_reducer());
            }

            template<class UReducer>
            static backing_delegate_impl_type construct(UReducer&& p_reducer)
            {
                return vex::core::make_shared_handle<
                    backing_delegate_impl<TSignature, TReducer>
                >(std::forward<UReducer>(p_reducer));
            }

            template<class TCreateFunc>
            bool compare_and_swap_loop(TCreateFunc p_create)
            {
                // should rewrite this so that no t_new l-value is required...
                backing_delegate_impl_type t_current = std::atomic_load( &m_backing_delegate );
                backing_delegate_impl_type t_new;
                do {
                    t_new = p_create( t_current );
                    // check for failure in "p_create":
                    if (!t_new) return false;
                } while ( !std::atomic_compare_exchange_strong( 
                 &m_backing_delegate, 
                 &t_current, 
                 std::move(t_new)) );

                return true;
            }
        };
    }

#define VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(InvokableEventImplClassName) \
    typedef typename base_type::reducer_type reducer_type; \
    typedef typename base_type::signature_type signature_type; \
    typedef typename base_type::delegate_type delegate_type; \
    typedef typename base_type::result_type result_type; \
    typedef typename base_type::backing_delegate_type backing_delegate_type; \
    typedef typename base_type::backing_delegate_impl_type backing_delegate_impl_type; \
    typedef typename base_type::invocation_list_type invocation_list_type; \
    \
    InvokableEventImplClassName() \
        : base_type() {} \
    \
    template<class UReducer> \
    InvokableEventImplClassName(UReducer&& p_reducer) \
        : base_type(std::forward<UReducer>(p_reducer)) {} \
    \
    InvokableEventImplClassName(invokable_event_default_impl&& p_other) \
        : base_type(std::move(p_other)) {} \
    \
    ~InvokableEventImplClassName() {}

    template<class TRet, class TReducer, class TContract>
    class invokable_event_default_impl<TRet (), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke() const
        {
            // the implementation gurantees that m_backing_delegate is never empty...
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke();
        }
    };

    template<class TRet, class TArg1, class TReducer, class TContract>
    class invokable_event_default_impl<TRet (TArg1), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (TArg1), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (TArg1), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke(TArg1 p_arg1) const
        {
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke(std::forward<TArg1>(p_arg1));
        }
    };

    template<
        class TRet, class TArg1, class TArg2, 
        class TReducer, 
        class TContract
    >
    class invokable_event_default_impl<TRet (TArg1, TArg2), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (TArg1, TArg2), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (TArg1, TArg2), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2) const
        {
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2));
        }
    };

    template<
        class TRet, class TArg1, class TArg2, class TArg3,
        class TReducer, 
        class TContract
    >
    class invokable_event_default_impl<TRet (TArg1, TArg2, TArg3), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (TArg1, TArg2, TArg3), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (TArg1, TArg2, TArg3), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3) const
        {
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3));
        }
    };

    template<
        class TRet, class TArg1, class TArg2, class TArg3, class TArg4,
        class TReducer, 
        class TContract
    >
    class invokable_event_default_impl<TRet (TArg1, TArg2, TArg3, TArg4), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (TArg1, TArg2, TArg3, TArg4), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (TArg1, TArg2, TArg3, TArg4), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3, TArg4 p_arg4) const
        {
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4));
        }
    };

    template<
        class TRet, class TArg1, class TArg2, class TArg3, class TArg4, class TArg5,
        class TReducer, 
        class TContract
    >
    class invokable_event_default_impl<TRet (TArg1, TArg2, TArg3, TArg4, TArg5), TReducer, TContract>
        : public detail::invokable_event_default_impl_base<TRet (TArg1, TArg2, TArg3, TArg4, TArg5), TReducer, TContract>
    {
    protected:
        typedef detail::invokable_event_default_impl_base<
            TRet (TArg1, TArg2, TArg3, TArg4, TArg5), TReducer, TContract
        > base_type;

        VEX_INVOKABLE_EVENT_DEFAULT_IMPL_BODY(invokable_event_default_impl);
    public:
        result_type invoke(TArg1 p_arg1, TArg2 p_arg2, TArg3 p_arg3, TArg4 p_arg4, TArg5 p_arg5) const
        {
            auto t_current = std::atomic_load(&m_backing_delegate);
            return t_current->invoke(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4),
                std::forward<TArg5>(p_arg5));
        }
    };
}}}